package org.orm.oop.core.mapping;

import org.orm.oop.api.Entity;
import org.orm.oop.api.EntityScan;
import org.orm.oop.common.util.OopClassUtils;
import org.orm.oop.core.context.AppContext;
import org.orm.oop.core.mapping.model.MappingXml;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;

import java.util.*;

import static org.orm.oop.core.util.BeanDefinitionUtils.EntityManagerFactoryBeanDefinition;
import static org.orm.oop.core.util.BeanDefinitionUtils.getEntityManagerFactoryBeanDefinitions;

public class EntityMappingPostProcessor implements BeanFactoryPostProcessor, Ordered {
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        if(!ConfigurableListableBeanFactory.class.isInstance(beanFactory)) {
            return;
        }

        ConfigurableListableBeanFactory factory = (ConfigurableListableBeanFactory) beanFactory;

        for (EntityManagerFactoryBeanDefinition definition : getEntityManagerFactoryBeanDefinitions(factory)) {

            BeanFactory definitionFactory = definition.getBeanFactory();

            if (!(definitionFactory instanceof BeanDefinitionRegistry)) {
                continue;
            }
            BeanDefinitionRegistry registry = (BeanDefinitionRegistry)beanFactory;

            Set<String> entityScanPackages = getEntityScanPackages(registry);

            for(String pakeageName : entityScanPackages) {
                Set<Class<?>> classes = OopClassUtils.getClasses(pakeageName);
                for(Class clazz : classes) {
                    Entity entity = AnnotationUtils.getAnnotation(clazz, Entity.class);
                    if(entity != null) {
                        MappingXml mappingXml = EntityResolveMappingXml.entityResolveMapping(clazz);
                        AppContext.INSTANCE.getMetaData().put(mappingXml.getClassName(), mappingXml);
                        new FreemarkerAutoMappingXml().autoMappingXml(mappingXml);
                    }
                }
            }
        }
    }

    protected Set<String> getEntityScanPackages(
            BeanDefinitionRegistry registry) {
        Set<String> packages = new LinkedHashSet<String>();
        String[] names = registry.getBeanDefinitionNames();
        for (String name : names) {
            BeanDefinition definition = registry.getBeanDefinition(name);
            if (definition instanceof AnnotatedBeanDefinition) {
                AnnotatedBeanDefinition annotatedDefinition = (AnnotatedBeanDefinition) definition;
                addComponentScanningPackages(packages,
                        annotatedDefinition.getMetadata());
            }
        }
        return packages;
    }

    private void addComponentScanningPackages(Set<String> packages,
                                              AnnotationMetadata metadata) {
        AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata
                .getAnnotationAttributes(EntityScan.class.getName(), true));
        if (attributes != null) {
            addPackages(packages, attributes.getStringArray("value"));
//            addPackages(packages, attributes.getStringArray("basePackages"));
//            addClasses(packages, attributes.getStringArray("basePackageClasses"));
            if (packages.isEmpty()) {
                packages.add(ClassUtils.getPackageName(metadata.getClassName()));
            }
        }
    }

    private void addPackages(Set<String> packages, String[] values) {
        if (values != null) {
            Collections.addAll(packages, values);
        }
    }

    private void addClasses(Set<String> packages, String[] values) {
        if (values != null) {
            for (String value : values) {
                packages.add(ClassUtils.getPackageName(value));
            }
        }
    }

    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE + 10;
    }
}
